package com.zxx.cascommon.base.utils;

import java.security.SecureRandom;

/**
 * 自定义 ID 生成器 17-19位数据 最长使用67年 ID 生成规则: ID长达 64 bits |
 * 1 bits 标志位|41 bits: Timestamp (毫秒)| 6 bits: 业务线标号| 6 bits: 机器编号 | 10 bits: 序列号 |
 * 支持63个业务线 63个机器 1023个序列号
 * @author zhoukai
 */
public class GenerateServiceIdUtils {

    /**
     * 基准时间 2018-05-02 13:14:27
     */
    private final static long BASE_TIME = 1525238067000L;
    /**
     * 业务标志位数
     */
    private final static long SERVICE_ID_BIT = 6L;
    /**
     * 机器标识位数
     */
    private final static long WORKER_ID_BIT = 6L;
    /**
     * 序列号识位数
     */
    private final static long SEQUENCE_ID_BIT = 10L;

    /**
     * 业务标志ID最大值
     */
    public final static long MAX_SERVICE_ID  = -1L ^ (-1L << SERVICE_ID_BIT);
    /**
     * 机器ID最大值
     */
    private final static long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BIT);
    /**
     * 序列号ID最大值
     */
    private final static long MAX_SEQUENCE_ID = -1L ^ (-1L << SEQUENCE_ID_BIT);

    /**
     * 机器ID偏左移
     */
    private final static long WORKER_ID_SHIFT = SEQUENCE_ID_BIT;
    /**
     * 业务ID偏左移
     */
    private final static long SERVICE_ID_SHIFT = SEQUENCE_ID_BIT + WORKER_ID_BIT;

    /**
     * 时间毫秒左移
     */
    private final static long TIME_SHIFT = SEQUENCE_ID_BIT + WORKER_ID_BIT + SERVICE_ID_BIT ;

    /**
     * 最后一次的时间戳
     **/
    private static long lastTimestamp = -1L;

    /**
     * 序列号初始值
     */
    private long sequence = 0L;
    /**
     * 机器号
     */
    private final long workerId;
    /**
     * 业务线ID
     */
    private final long serviceId;


    public GenerateServiceIdUtils(long workerId, long serviceId) {
        // 如果超出范围就抛出异常
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
        }
        if (serviceId > MAX_SERVICE_ID || serviceId < 0) {
            throw new IllegalArgumentException(
                "service Id can't be greater than %d or less than 0");
        }
        this.serviceId = serviceId;
        this.workerId = workerId;
    }

    /**
     * 实际产生代码的
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            try {
                throw new Exception(
                    "Clock moved backwards.  Refusing to generate id for " + (lastTimestamp
                        - timestamp) + " milliseconds");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //如果上次生成时间和当前时间相同,在同一毫秒内
        if (lastTimestamp == timestamp) {
            //sequence自增，因为sequence只有10bit，所以和sequenceMask相与一下，去掉高位
            sequence = (sequence + 1) & MAX_SEQUENCE_ID;
            //判断是否溢出,也就是每毫秒内超过1024，当为1024时，与sequenceMask相与，sequence就等于0
            if (sequence == 0) {
                //自旋等待到下一毫秒
                timestamp = tailNextMillis(lastTimestamp);
            }
        } else {
            // 如果和上次生成时间不同,重置sequence，就是下一毫秒开始，sequence计数重新从0开始累加,
            // 为了保证尾数随机性更大一些,最后一位设置一个随机数
            sequence = new SecureRandom().nextInt(100);
        }

        lastTimestamp = timestamp;

        return ((timestamp - BASE_TIME) << TIME_SHIFT)
            | (serviceId << SERVICE_ID_SHIFT) | (
            workerId << WORKER_ID_SHIFT) | sequence;
    }

    /**
     * 防止产生的时间比之前的时间还要小（由于NTP回拨等问题）,保持增量的趋势.
     *
     * @author zhoukai
     * @date 2018/3/26 下午8:27
     */
    private long tailNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    /**
     * 获取当前的时间戳
     *
     * @author zhoukai
     * @date 2018/3/26 下午8:27
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 获取生成时间
     * @param id
     * @author zhoukai
     * @date 2019/11/12 16:20
     */
    public static Long getTime(Long id) {
        String str = Long.toBinaryString(id);
        int size = str.length();
        int startIndex  = 0;
        int endIndex  = Math.toIntExact(size - SEQUENCE_ID_BIT - WORKER_ID_BIT - SERVICE_ID_BIT);
        String sequenceBinary = str.substring(startIndex, endIndex);
        return Long.parseLong(sequenceBinary, 2)+BASE_TIME;
    }

    /**
     * 获取序列号
     * @param id
     * @author zhoukai
     * @date 2019/11/12 16:20
     */
    public static Long getSequence(Long id) {
        String str = Long.toBinaryString(id);
        int size = str.length();
        int startIndex  = Math.toIntExact(size - SEQUENCE_ID_BIT);
        int endIndex = size;
        String sequenceBinary = str.substring(startIndex, endIndex);
        return Long.parseLong(sequenceBinary, 2);
    }

    /**
     * 获取机器编号
     * @param id
     * @author zhoukai
     * @date 2019/11/12 16:21
     */
    public static Long getWorkerId(Long id) {
        String str = Long.toBinaryString(id);
        int size = str.length();
        int startIndex  = Math.toIntExact(size - SEQUENCE_ID_BIT - WORKER_ID_BIT);
        int endIndex =  Math.toIntExact(size - SEQUENCE_ID_BIT);
        String sequenceBinary = str.substring(startIndex, endIndex);
        return Long.parseLong(sequenceBinary, 2);
    }

    /**
     * 获取业务线号
     * @param id
     * @author zhoukai
     * @date 2019/11/12 16:22
     */
    public static Long getServiceId(Long id) {
        String str = Long.toBinaryString(id);
        int size = str.length();
        int startIndex  = Math.toIntExact(size - SEQUENCE_ID_BIT - WORKER_ID_BIT - SERVICE_ID_BIT);
        int endIndex =  Math.toIntExact(size - SEQUENCE_ID_BIT - WORKER_ID_BIT );
        String sequenceBinary = str.substring(startIndex, endIndex);
        return Long.parseLong(sequenceBinary, 2);
    }


    public static void main(String[] args) {
        GenerateServiceIdUtils generateId = new GenerateServiceIdUtils(62,1);
        for (int i = 0; i < 10; i++) {
            System.out.println(generateId.nextId());
        }
        System.out.println(getTime(250354836925708313L));
        System.out.println(getServiceId(250354836925708313L));
        System.out.println(getWorkerId(250354836925708313L));
        System.out.println(getSequence(250354836925708313L));

        System.out.println(MAX_SERVICE_ID);
        System.out.println(MAX_WORKER_ID);
        System.out.println(MAX_SEQUENCE_ID);

    }
}
